﻿/*
 * @author: wizardc
 */

using System;
using DG.Tweening;
using UnityEngine;

namespace Dou.Controller
{
    /// <summary>
    /// 限制机位的限制类型
    /// </summary>
    internal enum RestrictCameraLimitType
    {
        /// <summary>
        /// 立方体
        /// </summary>
        Cubic,
        /// <summary>
        /// 梯形
        /// </summary>
        CubicTrapezoid
    }

    /// <summary>
    /// 高度设定
    /// </summary>
    public struct RestrictCameraHeightSetting
    {
        /// <summary>
        /// 高度
        /// </summary>
        public float height;
        /// <summary>
        /// x 轴角度
        /// </summary>
        public float degree;
        /// <summary>
        /// 高度拉伸速度（相机越高缩放时需要增加/减少的高度越多）
        /// </summary>
        public float speed;
    }
    
    /// <summary>
    /// 限制机位的摄像机控制器，多用于固定机位的场景，如SLG中的主城相机位
    ///
    /// - 需要场景里加一个地面碰撞体进行配合（注意只能是一个模型碰撞体），并且应该使这个碰撞体始终都填满可视区域；
    /// - 拖动控制XZ平面，捏合控制 Y 轴；
    /// - 摄像机类型支持透视和正交；
    /// - 限定区域为3D区域，限定区域是类似AABB的设计不存在旋转的说法，限定区域是限定摄像机的位置而非可视区域；
    /// - 限制区域支持立方体和立方梯形；
    /// - 不限制相机的旋转；
    /// - 无获取可视区域相关的功能；
    /// 
    /// 待完善：
    /// 1. 大地图缩放时不跟手，只能调节参数尽可能使其跟手，需要找到跟手的算法
    /// 2. 不同的设备屏幕分辨率是不同的，所以要统一下类似移动速度等参数
    /// </summary>
    [RequireComponent(typeof(Camera))]
    public class RestrictCameraController : MonoBehaviour
    {
        /// <summary>
        /// 相机位置或高度变动时
        /// </summary>
        public event Action<RestrictCameraController> onPositionChanged;
        
        /// <summary>
        /// 开始拖动时
        /// </summary>
        public event Action<RestrictCameraController> onMoveBegin;
        
        /// <summary>
        /// 拖动中
        /// </summary>
        public event Action<RestrictCameraController> onMove;
        
        /// <summary>
        /// 结束拖动时
        /// </summary>
        public event Action<RestrictCameraController> onMoveEnd;
        
        /// <summary>
        /// 开始缩放时
        /// </summary>
        public event Action<RestrictCameraController> onScaleBegin;
        
        /// <summary>
        /// 缩放中
        /// </summary>
        public event Action<RestrictCameraController> onScale;
        
        /// <summary>
        /// 结束缩放时
        /// </summary>
        public event Action<RestrictCameraController> onScaleEnd;

        /// <summary>
        /// 射线检测最大距离
        /// </summary>
        public float rayCastMaxDistance = 1000;

        /// <summary>
        /// 射线检测层
        /// </summary>
        public int rayCastLayerMask = ~0;
        
        private Camera _camera;

        private RestrictCameraLimitType _limitType;
        
        // 限制区域相关坐标信息
        private Vector3[] _cubicLimitPos;
        private Vector3[] _trapezoidLimitPos;
        
        private RestrictCameraHeightSetting[] _heightSettings;
        private float _minHeight;
        private float _maxHeight;

        private Ease _inertiaEase;
        private float _inertiaEffectiveTime;
        private Vector2 _inertia;
        private Vector2 _inertiaSpeed;
        private float _inertiaTime;
        
        private float _height;

        private bool _moving;
        private Vector3 _lastCameraPos;
        private Vector3 _lastCastPos;

        private Tweener _moveTweener;
        private float _tweenLastTime;
        private Vector3 _tweenVector;

        private Vector3 _lastPinchPos;
        private Tweener _pinchTweener;
        
        public RestrictCameraController()
        {
            _cubicLimitPos = new []
            {
                Vector3.zero, Vector3.zero
            };
            _trapezoidLimitPos = new []
            {
                Vector3.zero, Vector3.zero, Vector3.zero, Vector3.zero,
                Vector3.zero, Vector3.zero, Vector3.zero, Vector3.zero
            };
        }

        /// <summary>
        /// 关联的摄像机对象
        /// </summary>
        public Camera camera => _camera;
        
        /// <summary>
        /// 立方体限制区域
        /// </summary>
        public Vector3[] cubicLimitPos => _cubicLimitPos;
        
        /// <summary>
        /// 立方梯形限制区域
        /// </summary>
        public Vector3[] trapezoidLimitPos => _trapezoidLimitPos;

        void Awake()
        {
            _camera = GetComponent<Camera>();
        }

        /// <summary>
        /// 初始化立方体限制框
        /// </summary>
        public void InitCubicLimit(Vector3 min, Vector3 max)
        {
            _limitType = RestrictCameraLimitType.Cubic;
            _cubicLimitPos[0] = min;
            _cubicLimitPos[1] = max;
        }

        /// <summary>
        /// 初始化立方梯形限制框
        /// </summary>
        public void InitCubicTrapezoid(Vector3 underTopLeft, Vector3 underTopRight, Vector3 underBottomLeft, Vector3 underBottomRight,
            Vector3 aboveTopLeft, Vector3 aboveTopRight, Vector3 aboveBottomLeft, Vector3 aboveBottomRight)
        {
            _limitType = RestrictCameraLimitType.CubicTrapezoid;
            _trapezoidLimitPos[0] = underTopLeft;
            _trapezoidLimitPos[1] = underTopRight;
            _trapezoidLimitPos[2] = underBottomLeft;
            _trapezoidLimitPos[3] = underBottomRight;
            _trapezoidLimitPos[4] = aboveTopLeft;
            _trapezoidLimitPos[5] = aboveTopRight;
            _trapezoidLimitPos[6] = aboveBottomLeft;
            _trapezoidLimitPos[7] = aboveBottomRight;
        }
        
        /// <summary>
        /// 初始化高度设定，按高度值从小到大排序传入
        /// </summary>
        public void InitHeightSetting(RestrictCameraHeightSetting[] settings)
        {
            _heightSettings = settings;
            _minHeight = _heightSettings[0].height;
            _maxHeight = _heightSettings[^1].height;
            // 将摄像机初始化为最小高度的状态
            var setting = _heightSettings[0];
            transform.position = new Vector3(0, setting.height, 0);
            transform.rotation = Quaternion.Euler(setting.degree, 0, 0);
        }
        
        /// <summary>
        /// 初始化移动缓动效果设定
        /// </summary>
        /// <param name="effectiveTime">生效时间，即调用 BeginMove 后在该时间内调用 EndMove 才会有惯性滑动效果</param>
        public void InitMoveTween(Ease ease, float effectiveTime, Vector2 inertia, Vector2 inertiaSpeed, float inertiaTime)
        {
            _inertiaEase = ease;
            _inertiaEffectiveTime = effectiveTime;
            _inertia = inertia;
            _inertiaSpeed = inertiaSpeed;
            _inertiaTime = inertiaTime;
        }
        
        /// <summary>
        /// 立即移动到指定位置，该位置是世界坐标
        /// </summary>
        public void MoveTo(Vector3 position)
        {
            StopMoveTween();
            
            transform.position = position;
            CheckLimit();
        }

        private bool CheckLimit()
        {
            var pos = transform.position;
            var adjustPos = Vector3.zero;
            adjustPos = _limitType == RestrictCameraLimitType.Cubic ? RestrictCameraUtil.CubicLimit(pos, _cubicLimitPos) : RestrictCameraUtil.TrapezoidLimit(pos, _trapezoidLimitPos, _minHeight, _maxHeight);
            if (Vector3.Distance(pos, adjustPos) >= 0.01f)
            {
                transform.position = adjustPos;
                _lastCameraPos = transform.position;
                return true;
            }
            return false;
        }

        /// <summary>
        /// 持续移动，该位置为屏幕坐标
        /// </summary>
        public void Move(float x, float y)
        {
            if (RestrictCameraUtil.ScreenPointToGroundPoint(_camera, x, y, rayCastMaxDistance, rayCastLayerMask, out var pos))
            {
                if (_moving)
                {
                    var move = _lastCastPos - pos;
                    transform.position = _lastCameraPos + move;
                    _lastCameraPos = transform.position;
                    RestrictCameraUtil.ScreenPointToGroundPoint(_camera, x, y, rayCastMaxDistance, rayCastLayerMask, out _lastCastPos);
                    
                    _tweenLastTime = Time.realtimeSinceStartup;
                    _tweenVector = move;
                    
                    CheckLimit();
                    
                    onMove?.Invoke(this);
                    onPositionChanged?.Invoke(this);
                }
                else
                {
                    StopMoveTween();
                    
                    _moving = true;
                    _lastCameraPos = transform.position;
                    
                    _tweenLastTime = Time.realtimeSinceStartup;
                    _lastCastPos = pos;
                    
                    onMoveBegin?.Invoke(this);
                }
            }
        }
        
        /// <summary>
        /// 结束持续移动
        /// 调用后会触发后续的缓动效果，需要先调用 BeginMove 方法
        /// </summary>
        public void EndMove()
        {
            if (_moving)
            {
                _moving = false;
                onMoveEnd?.Invoke(this);
                
                StartMoveTween();
            }
        }

        private void StartMoveTween()
        {
            if (_moveTweener != null)
            {
                return;
            }

            if (Time.realtimeSinceStartup - _tweenLastTime > _inertiaEffectiveTime)
            {
                return;
            }
            
            var ratio = (transform.position.y - _minHeight) / (_maxHeight - _minHeight);
            var inertia = Mathf.Lerp(_inertia.x, _inertia.y, ratio);
            var inertiaSpeed = Mathf.Lerp(_inertiaSpeed.x, _inertiaSpeed.y, ratio);
            _tweenVector.y = 0;
            _tweenVector *= inertia;
            var distance = _tweenVector.magnitude;
            var time = distance / inertiaSpeed;
            var dist = transform.position + _tweenVector;
            JumpToPos(dist, time > _inertiaTime ? _inertiaTime : time, _inertiaEase);
        }
        
        /// <summary>
        /// 持续缩放
        /// </summary>
        public void Scale(float x, float y, float deltaPinch)
        {
            StopMoveTween();

            if (_lastPinchPos == default)
            {
                RestrictCameraUtil.ScreenPointToGroundPoint(_camera, x, y, rayCastMaxDistance, rayCastLayerMask, out _lastPinchPos);
                onScaleBegin?.Invoke(this);
            }
            
            var setting = GetCurrentSetting(transform.position.y);
            float height = setting.height + -deltaPinch * setting.speed;
            height = Mathf.Clamp(height, _minHeight, _maxHeight);
            if ((setting.height == _maxHeight && height > setting.height) || (setting.height == _minHeight && height < setting.height))
            {
                return;
            }
            
            transform.rotation = Quaternion.Euler(setting.degree, 0, 0);
            
            RestrictCameraUtil.ScreenPointToGroundPoint(_camera, x, y, rayCastMaxDistance, rayCastLayerMask, out var pinchPos);

            var pos = transform.position;
            pos -= pinchPos - _lastPinchPos;
            pos.y = height;
            transform.position = pos;
            
            CheckLimit();
            
            onScale?.Invoke(this);
            onPositionChanged?.Invoke(this);
        }

        private RestrictCameraHeightSetting GetCurrentSetting(float height)
        {
            height = Mathf.Clamp(height, _minHeight, _maxHeight);

            var nowLevel = 0;
            for (var i = _heightSettings.Length - 2; i >= 0; i--)
            {
                if (height >= _heightSettings[i].height)
                {
                    nowLevel = i;
                    break;
                }
            }

            var ratio = (height - _heightSettings[nowLevel].height) / (_heightSettings[nowLevel + 1].height - _heightSettings[nowLevel].height);
            var setting = new RestrictCameraHeightSetting
            {
                height = height,
                degree = Mathf.Lerp(_heightSettings[nowLevel].degree, _heightSettings[nowLevel + 1].degree, ratio),
                speed = Mathf.Lerp(_heightSettings[nowLevel].speed, _heightSettings[nowLevel + 1].speed, ratio)
            };
            
            return setting;
        }
        
        /// <summary>
        /// 结束持续缩放
        /// </summary>
        public void EndScale()
        {
            _lastPinchPos = default;
            onScaleEnd?.Invoke(this);
        }

        /// <summary>
        /// 跳转到指定位置
        /// </summary>
        public void JumpToPos(Vector3 position, float time, Ease ease, Action onComplete = null)
        {
            StopMoveTween();
            
            _moveTweener = transform.DOMove(position, time);
            _moveTweener.SetEase(ease);
            _moveTweener.OnUpdate(() =>
            {
                var setting = GetCurrentSetting(transform.position.y);
                transform.rotation = Quaternion.Euler(setting.degree, 0, 0);
                if (CheckLimit())
                {
                    // 允许继续缓动
                    // StopMoveTween();
                    // onComplete?.Invoke();
                }
                onPositionChanged?.Invoke(this);
            });
            _moveTweener.OnComplete(() =>
            {
                var setting = GetCurrentSetting(transform.position.y);
                transform.rotation = Quaternion.Euler(setting.degree, 0, 0);
                CheckLimit();
                StopMoveTween();
                onPositionChanged?.Invoke(this);
                onComplete?.Invoke();
            });
            _moveTweener.Play();
        }
        
        private void StopMoveTween()
        {
            if (_moveTweener != null)
            {
                _moveTweener.Kill();
                _moveTweener = null;
            }
        }
        
#if UNITY_EDITOR

        void LateUpdate()
        {
            if (Input.mouseScrollDelta.y != 0)
            {
                Scale(Input.mousePosition.x, Input.mousePosition.y, Input.mouseScrollDelta.y * 10);
                EndScale();
            }
        }

#endif

    }
}
